<?php
/**
 * Created by PhpStorm.
 * User: meng
 * Date: 2023/10/9
 * Time: 10:38
 */

namespace app\admin\service;

use Carbon\Carbon;
use Hidehalo\Nanoid\Client;
use Illuminate\Support\Arr;
use lib\common\CommonService;
use lib\exception\FailureException;
use lib\exception\ParamsException;
use model\FileModel;
use Webman\Config;
use Webman\Http\UploadFile;

class FileService extends CommonService
{
    private FileModel $fileModel;
    private Client $nanoid;

    public function __construct (FileModel $fileModel) {
        parent::__construct();
        $this->fileModel = $fileModel;
        $this->nanoid = new Client();
    }

    public function uploadSingle (UploadFile $file, $disk = 'public'): array {
        $config = $this->getConfig($disk);
        return $this->putFile($file, $disk, $config);
    }

    /**
     * @param UploadFile[] $files
     * @param string $disk
     * @return array
     */
    public function uploadMultiple (array $files, string $disk = 'public'): array {
        $config = $this->getConfig($disk);
        $ret = [];
        foreach ($files as $file) {
            $ret[] = $this->putFile($file, $disk, $config);
        }
        return $ret;
    }

    public function putFile (UploadFile $file, $disk, $config = null): array {
        if (empty($config)) {
            $config = $this->getConfig($disk);
        }
        if (!$file->isValid()) {
            throw new ParamsException('未找到文件');
        }
        $this->verifyExt($file, $config);
        $this->verifyFileSize($file, $config);
        $hash = $this->getHash($file);
        $exist = $this->fileModel->getFileByHash($hash, $disk);
        if (!empty($exist)) {
            return [
                'id'          => $exist['id'],
                'path'        => $exist['path'],
                'origin_name' => $exist['origin_name'],
                'size'        => $exist['size'],
                'file_hash'   => $exist['file_hash'],
                'type'        => $exist['type']
            ];
        }
        $dir = $this->getSaveDir($config);
        $ext = $file->getUploadExtension();
        $originName = $file->getUploadName();
        $size = $file->getSize();
        $fileName = $this->nanoid->formattedId('0123456789abcdefhijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTUVWXYZ', 32);
        if (!empty($ext)) {
            $fileName = $fileName . '.' . $ext;
        }
        $fullPath = cms_join_paths($dir, $fileName);
        $path = str_replace($config['root'], '', $fullPath);
        $file->move($fullPath);
        $fileModel = $this->fileModel::create([
            'path'        => $path,
            'origin_name' => $originName,
            'ext'         => $ext,
            'size'        => $size,
            'file_hash'   => $hash,
            'type'        => $disk,
            'create_time' => Carbon::now()->toDateTimeString()
        ]);
        return [
            'id'          => $fileModel['id'],
            'path'        => $path,
            'origin_name' => $fileModel['origin_name'],
            'size'        => $fileModel['size'],
            'file_hash'   => $fileModel['file_hash'],
            'type'        => $disk,
        ];
    }

    protected function getSaveDir ($config): string {
        $root = $config['root'];
        $dir = cms_join_paths($root, 'storage', Carbon::now()->format('Ymd'));
        if (!is_dir($dir)) {
            mkdir($dir, 0777, true);
        }
        return $dir;
    }

    protected function verifyFileSize (UploadFile $file, $config): void {
        $limit = $config['single_limit'];
        if ($file->getSize() > $limit) {
            throw new ParamsException('文件: ' . $file->getUploadName() . '超出大小限制');
        }
    }

    protected function verifyExt (UploadFile $file, $config): void {
        if (!empty($config['include'])) {
            $ext = $file->getUploadExtension();
            if (empty($ext) || !in_array($ext, $config['include'])) {
                throw new ParamsException('文件类型不合法');
            }
        } else if (!empty($config['exclude'])) {
            if (empty($ext) || in_array($ext, $config['exclude'])) {
                throw new ParamsException('文件类型不合法');
            }
        }
    }

    protected function getConfig ($disk) {
        $config = Config::get('filesystem');
        if (empty($config)) {
            throw new FailureException('配置文件: filesystem.php不存在');
        }
        $requiredKeys = ['single_limit', 'total_limit', 'nums', 'include', 'exclude'];
        foreach ($requiredKeys as $key) {
            if (!isset($config[$key])) {
                throw new FailureException('配置文件 filesystem.php 中字段 ' . $key . ' 不存在');
            }
        }
        $disks = $config['disks'];
        $diskConfig = null;
        foreach ($disks as $name => $item) {
            if ($name == $disk) {
                $diskConfig = $item;
                break;
            }
        }
        if (empty($diskConfig)) {
            throw new FailureException('配置文件中 disk:' . $disk . ' config 不存在');
        }
        $diskConfig['single_limit'] = Arr::get($diskConfig, 'single_limit', $config['single_limit']);
        $diskConfig['total_limit'] = Arr::get($diskConfig, 'total_limit', $config['total_limit']);
        $diskConfig['nums'] = Arr::get($diskConfig, 'nums', $config['nums']);
        $diskConfig['include'] = Arr::get($diskConfig, 'include', $config['include']);
        $diskConfig['exclude'] = Arr::get($diskConfig, 'exclude', $config['exclude']);
        return $diskConfig;
    }

    private function getHash (UploadFile $file): string {
        return hash_file('md5', $file->getRealPath());
    }
}
